from nicegui import ui, app, globals
from nicegui.element import Element
from typing import Any, Callable, Dict, List, Optional, Set, cast
from typing_extensions import get_args
from dataclasses import dataclass
from nicegui.dataclasses import KWONLY_SLOTS
from nicegui.events import handle_event, UiEventArguments
from ex4nicegui import to_ref, ref_computed, effect
from ex4nicegui.utils.signals import Ref
from ex4nicegui.reactive import rxui
from nicegui.tailwind_types.gap import Gap
from pathlib import Path
import csv

CURRENT_DIR = Path(__file__).parent
SETTING_DATA_FILE = CURRENT_DIR / "layout_helper-setting-data.csv"


@dataclass
class TSettingData:
    key: str
    pre: str
    init_value: Any
    select_options: Dict[str, str]
    title: str


# region 设置项数据


def load_setting_items():
    globals_dict = {}
    locals_dict = {}

    def paser_options(opt_str: str, opt_type: str):
        assert opt_type in ["dict", "code"], "选项类型必需是指定枚举值之一"

        if opt_type == "dict":
            return eval(opt_str)

        if opt_type == "code":
            exec(opt_str, globals_dict, locals_dict)
            return locals_dict["_"]

        return {}

    with open(SETTING_DATA_FILE, "r", encoding="utf8") as f:
        reader = csv.reader(f)

        # header
        next(reader)

        for row in reader:
            opt_str = row[3]
            opt_type = row[4]
            row[3] = paser_options(opt_str, opt_type)  # type: ignore

            args = [*row[:4], *row[5:]]
            yield TSettingData(*args)  # type: ignore


setting_items = list(load_setting_items())

# endregion

# region css静态资源
_G_NG_clients: Set[str] = set()


def inject_static():
    cur_client = globals.get_client()
    # if cur_client.shared:
    #     return

    if cur_client.id in _G_NG_clients:
        return

    app.add_static_file(
        local_file=Path(__file__).parent / "LayoutHelper.css",
        url_path="/LayoutHelper.css",
    )
    ui.add_head_html(
        """
        <link rel="stylesheet" href="LayoutHelper.css">
    """
    )

    _G_NG_clients.add(cur_client.id)


# endregion


# region LayoutHelper 功能类定义


@dataclass(**KWONLY_SLOTS)
class LayoutHelperEventArguments(UiEventArguments):
    componentType: Optional[str]
    classes: List


_LayoutHelper_Args = list(LayoutHelperEventArguments.__annotations__.keys())


class LayoutHelper(Element, component="LayoutHelper.js"):
    def __init__(self) -> None:
        super().__init__()

        pres = [item.pre for item in setting_items]
        self._props["pre-classes"] = pres
        self._props["closest-query"] = ".ex4ng-ng_cp"
        self._props["selectId"] = "layout-helper"
        self._props["component-type-attr"] = "data-cp-type"

    def on_selected_data_changed(
        self, handler: Optional[Callable[[LayoutHelperEventArguments], Any]]
    ):
        def inner_handler(e):
            args = e.args
            handle_event(
                handler,
                LayoutHelperEventArguments(
                    sender=self,
                    client=self.client,
                    componentType=args["componentType"],
                    classes=args["classes"],
                ),
            )

        self.on("selectedDataChanged", inner_handler, _LayoutHelper_Args)

    def set_classes(self, names: List[str]):
        self.run_method("setClasses", names)

    def remove_classes_by_pres(self, pres: List[str]):
        self.run_method("removeClassesByPre", pres)


class use_layout_helper_result:
    def __init__(self) -> None:
        self.__lh = LayoutHelper()

        self.__componentType = to_ref(cast(Optional[str], None))
        self.__classes = to_ref(cast(List[str], []))

        def on_selected_data_changed(e: LayoutHelperEventArguments):
            self.__componentType.value = e.componentType or None
            self.__classes.value = e.classes

        self.__lh.on_selected_data_changed(on_selected_data_changed)

    @property
    def LayoutHelper(self):
        return self.__lh

    @property
    def componentType(self):
        return self.__componentType

    @property
    def classes(self):
        return self.__classes

    def set_classes(self, names: List[str]):
        self.__lh.set_classes(names)

    def remove_classes_by_pres(self, pres: List[str]):
        self.__lh.remove_classes_by_pres(pres)


def use_layout_helper():
    return use_layout_helper_result()


# endregion


# region 魔改 nicegui 组件构造
_EXCEPT_COMPONENTS = (
    "element",
    "aggrid",
    "audio",
    "avatar",
    "badge",
    "button",
    "card",
    "carousel",
    "carousel_slide",
    "chart",
    "chat_message",
    "checkbox",
    "color_input",
    "color_picker",
    "column",
    "date",
    "dialog",
    "expansion",
    "grid",
    "icon",
    "image",
    "input",
    "interactive_image",
    "joystick",
    "knob",
    "label",
    "line_plot",
    "link",
    "link_target",
    "markdown",
    "menu",
    "mermaid",
    "number",
    "plotly",
    "circular_progress",
    "linear_progress",
    "pyplot",
    "radio",
    "row",
    "scene",
    "select",
    "slider",
    "step",
    "stepper",
    "stepper_navigation",
    "switch",
    "table",
    "tab",
    "tabs",
    "textarea",
    "time",
    "toggle",
    "tree",
    "upload",
    "video",
    "footer",
    "header",
    "page_sticky",
)


def _setup_ui_component():
    ng_ct = ui.query(".nicegui-content")
    ng_ct.classes("ex4ng-ng_cp")
    ng_ct.props("data-cp-type=nicegui-content")

    for cp_name in _EXCEPT_COMPONENTS:
        if not hasattr(ui, cp_name):
            continue

        method = getattr(ui, cp_name)

        def wrap(method=method, cp_name=cp_name):
            def new_method(*args, **kws):
                ele = method(*args, **kws)
                ele.classes("ex4ng-ng_cp")
                ele._props["data-cp-type"] = type(ele).__name__
                if cp_name == "input":
                    cast(ui.input, ele).props("readonly")
                return ele

            return new_method

        setattr(ui, cp_name, wrap())


# endregion


# region 构造界面


def create_drawer():
    inject_static()

    _setup_ui_component()
    # init objects
    lh = use_layout_helper()

    has_target = ref_computed(lambda: lh.componentType.value is not None)

    @dataclass
    class TSetting:
        pre: str
        ref: Ref
        select_options: Dict[str, str]
        title: str

    def setup_setting():
        settings_dict = cast(Dict[str, TSetting], {})

        def on_selected_data_changed(e: LayoutHelperEventArguments):
            for key, item in settings_dict.items():
                for c in e.classes:
                    if c.startswith(item.pre):
                        settings_dict[key].ref.value = c
                        break
                else:
                    settings_dict[key].ref.value = ""

        lh.LayoutHelper.on_selected_data_changed(on_selected_data_changed)

        for item in setting_items:
            settings_dict[item.key] = TSetting(
                item.pre, to_ref(item.init_value), item.select_options, item.title
            )

            @effect
            def _(key=item.key):
                lh.remove_classes_by_pres([settings_dict[key].pre])
                lh.set_classes([settings_dict[key].ref.value])

        return settings_dict

    settings_dict = setup_setting()

    @ref_computed
    def all_classes():
        return (
            f".classes('{' '.join(item.ref.value for item in settings_dict.values())}')"
        )

    with rxui.drawer("right").classes("no-layout-helper"):
        with rxui.column().bind_visible(has_target).classes("grow"):
            with ui.row():
                rxui.label("类型：")
                rxui.label(lh.componentType)  # type: ignore

            rxui.label(all_classes)

            with ui.scroll_area().props("fit").classes("grow"):
                for key, item in settings_dict.items():
                    with ui.element("q-list").props("bordered separator"):
                        with ui.element("q-item").props("clickable v-ripple"):
                            with ui.element("q-section").classes("w-full"):
                                ui.label(item.title)
                                rxui.select(item.select_options, value=item.ref).props(
                                    "clearable"
                                )

                        # with ui.row().classes("items-end w-full flex-nowrap my-2"):
                        #     ui.label(item.title)
                        #     rxui.select(item.select_options, value=item.ref).props(
                        #         "clearable"
                        #     ).classes("grow")


# endregion


# 最后创建侧栏菜单。所以，你自己的界面就不要用侧栏了(以后再想其他方案)
create_drawer()

# 运行后，试试鼠标移动某些组件上方。
# 并且点击某个组件。右侧栏可以动态修改组件布局属性。
# 上方生成当前布局的 classes 设置代码
